'''
Created on Jul 9, 2011

@author: kafkef
'''

from PyQt4.QtGui import *
from PyQt4.QtCore import Qt, QModelIndex, QVariant, SIGNAL, QAbstractTableModel,\
    QTimer

from dexen.system.client import ui
from dexen.system import client
from dexen.system.client import db
from dexen.system.common.base import Action
from dexen.system.common.transact import RecvTransact
from dexen.system.client.ui.dialogs.action_info_dlg import ActionInfoDlg
from dexen.system.client.ui.dialogs.export_dlg import ExportDialog
from dexen.system.client.ui.widgets.table_view import TableView
from dexen.system.client.controller import timeIt


svr_proxy = client.GetServerProxy()


#===============================================================================
# Classes
#===============================================================================
class JobLogModel(QAbstractTableModel):
    def __init__(self, proxy_model, parent = None):
        super(JobLogModel, self).__init__(parent)
        self._header_items = []
        self._content_items = []
        self._content_str = ""
        self._actions = []
        self.proxy_model = proxy_model
    
    def rowCount(self, index=QModelIndex()):
        return len(self._content_items)
    
    def columnCount(self, index=QModelIndex()):
        if len(self._content_items) == 0:
            return 0
        return len(self._content_items[0])

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole or self._header_items == []:
            return QVariant()
        if orientation == Qt.Horizontal:
            return QVariant(self._header_items[section])
        if orientation == Qt.Vertical:
            return QVariant(section + 1)

    def data(self, index, role=Qt.DisplayRole):
        assert isinstance(index, QModelIndex)
        if not index.isValid(): return QVariant()
        
        if role == Qt.BackgroundRole:
            action = self._actions[index.row()]
            assert isinstance(action, Action)
            if action.has_slave_crashed() or action.has_task_failed():
                return QColor(255, 0, 0, 190)
            if action.is_master():
                return QColor(0, 0, 100, 150)
            if action.is_receive():
                return QColor(0x4E, 0x92, 0x58, 128)
            return QColor(Qt.lightGray)
        
        if role == Qt.ForegroundRole:
            action = self._actions[index.row()]
            assert isinstance(action, Action)
            if action.is_master() or action.has_slave_crashed()\
                                  or action.has_task_failed():
                return QColor(Qt.white)
            return QColor(Qt.black)
         
        if role == Qt.DisplayRole:
            return QVariant(self._content_items[index.row()][index.column()])

    def get_log(self):
        return self._content_str

    def set_proxy_model(self, proxy_model):
        self.proxy_model = proxy_model  
    
    def get_proxy_model(self):
        return self.proxy_model
    
    @timeIt
    def add_actions(self, actions):
        if len(actions) == 0:
            return
        
        if len(self._header_items) == 0:
            self._set_header(actions[-1])
            
        self._actions.extend(actions)
        
        action_items = []
        action_logs = []
        for action in actions:
            assert isinstance(action, Action)
            log_str = action.get_log()
            log_items = log_str.split(",")
            action_items.append(log_items)
            action_logs.append(log_str)
            
        self._add_logs(action_items, action_logs)
    
    def _set_header(self, action):
        assert isinstance(action, Action)
        header = action.get_header()
        self._header_items = header.split(",")
        self._header_tooltips = action.get_header_tooltip().split(",")
        
        assert self.columnCount() == 0
        self.beginInsertColumns(QModelIndex(), 0, len(self._header_items) - 1)
        self.endInsertColumns()
        
        self._content_str = header
    
    def _add_logs(self, content_items, content_logs):
        cur_pos = len(self._content_items)
        n_rows = len(content_items)
        
        if n_rows == 0: return
        
        self.beginInsertRows(QModelIndex(), cur_pos, cur_pos + n_rows - 1)
        self._content_items.extend(content_items)
        self.endInsertRows()
                
        content_str = "\n".join(content_logs)
        self._content_str += "\n" + content_str

    def get_action(self, index):
        assert isinstance(index, QModelIndex)
        return self._actions[index.row()]


class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        QSortFilterProxyModel.__init__(self, parent)
    
    def lessThan(self, left, right):
        assert isinstance(left, QModelIndex)
        assert isinstance(right, QModelIndex)
        
        leftData = self.sourceModel().data(left)
        rightData = self.sourceModel().data(right)
        
        assert isinstance(leftData, QVariant)
        assert isinstance(rightData, QVariant)
        
        if left.column() == 0 or left.column() == 1:
            return leftData.toInt() < rightData.toInt()
        
        return leftData.toString() < rightData.toString()

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if orientation == Qt.Horizontal and role == Qt.ToolTipRole:
            model = self.sourceModel()
            assert isinstance(model, JobLogModel)
            return QVariant(model._header_tooltips[section])

        if role != Qt.DisplayRole:
            return QVariant()
        if orientation == Qt.Horizontal:
            if self.sourceModel():
                return self.sourceModel().headerData(section, orientation, role)
            return None
        if orientation == Qt.Vertical:
            return QVariant(section + 1)

    def get_content(self):
        model = self.sourceModel()
        assert isinstance(model, JobLogModel)
        content = ",".join(model._header_items) + "\n"
        for i in xrange(self.rowCount()):
            line = ""
            cols = self.columnCount()
            for j in xrange(cols - 1):
                variant = self.data(self.index(i,j))
                assert isinstance(variant, QVariant)
                line += str(variant.toString()) + ","
            line += str(self.data(self.index(i, cols-1)).toString())
            content += line + "\n"
        return content        


class ExecutionLogPage(QWidget):
    def __init__(self):
        super(ExecutionLogPage, self).__init__()
        self.cur_job = None
        self.models = {} #JobInfo: BaseTableModel
        self.zeroTimer = QTimer(self)
        self._setupWidgets()

    def _setupWidgets(self):
        # Create widgets
        self.tableView = TableView()
        self.exportBtn = QPushButton("Export")
        self.refreshBtn = QPushButton("Refresh")
        self.autoRefreshCheckBox = QCheckBox("Auto Refresh")
        
        # Configure widgets
        self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableView.resizeColumnsToContents()
        self.refreshBtn.setEnabled(False)
        self.autoRefreshCheckBox.setEnabled(False)
        self.exportBtn.setEnabled(False)
        
        # Layout widgets
        hLayout = QHBoxLayout()
        hLayout.addStretch()
        hLayout.addWidget(self.autoRefreshCheckBox)
        hLayout.addWidget(self.refreshBtn)
        hLayout.addWidget(self.exportBtn)
        layout = QVBoxLayout()
        layout.addLayout(hLayout)
        layout.addWidget(self.tableView)
        self.setLayout(layout)

        # Define actions
        self.actionInfoAct = QAction("Show Info", self)
        
        # Configure signal/slot connections
        self.connect(self.autoRefreshCheckBox, SIGNAL("stateChanged(int)"),
                     self.on_autoRefreshCheckBox_changed)
        self.connect(self.zeroTimer, SIGNAL("timeout()"), self.on_timer_expired)
        self.connect(self.exportBtn, SIGNAL("clicked()"), self.on_exportBtn_clicked)
        self.connect(self.refreshBtn, SIGNAL("clicked()"), self.on_refreshBtn_clicked)
        self.connect(self.tableView, SIGNAL("customContextMenuRequested(const QPoint &)"), 
                     self._show_context_menu)
        self.connect(self.actionInfoAct, SIGNAL("triggered()"), self.show_action_info_dlg)

    def _show_context_menu(self, point):
        menu = QMenu(self.tableView)
        menu.addAction(self.actionInfoAct)
        self.actionInfoAct.setData(point)
        menu.exec_(self.tableView.mapToGlobal(point))

    def get_model(self, job_info):
        assert isinstance(job_info, db.JobInfo)
        if not self.models.has_key(job_info):
            self._construct_model(job_info)
        assert self.models.has_key(job_info)
        return self.models[job_info]
    
    def _construct_model(self, job_info):
        proxy_model = SortFilterProxyModel()
        proxy_model.setDynamicSortFilter(True)
        self.models[job_info] = JobLogModel(proxy_model)
        proxy_model.setSourceModel(self.models[job_info])
    
    def get_proxy_model(self, job_info):
        model = self.get_model(job_info)
        assert isinstance(model, JobLogModel)
        return model.get_proxy_model()
    
    def on_connect(self):
        if self.cur_job:
            self.autoRefreshCheckBox.setEnabled(True)
            if self.autoRefreshCheckBox.isChecked():
                self.zeroTimer.start()
            self.refreshBtn.setEnabled(True)
    
    def on_disconnect(self):
        self.zeroTimer.stop()
        self.autoRefreshCheckBox.setEnabled(False)
        self.refreshBtn.setEnabled(False)
                
    def show_action_info_dlg(self):
        point = self.actionInfoAct.data().toPoint()
        index = self.tableView.indexAt(point)
        assert isinstance(index, QModelIndex)
        proxy_model = self.get_proxy_model(self.cur_job)
        model = self.get_model(self.cur_job)

        assert isinstance(proxy_model, SortFilterProxyModel)
        assert isinstance(model, JobLogModel)

        print "row, column", index.row(), " ", index.column()
        index = proxy_model.mapToSource(index)
        action = model.get_action(index)
        assert isinstance(action, Action)
        dlg = ActionInfoDlg(action, self)
        dlg.exec_()
    
    def on_close_file(self):
        self.models.clear()
        self.cur_job = None
        self.tableView.setModel(SortFilterProxyModel())
    
    def on_delete_job(self, job_info):
        if self.models.has_key(job_info):
            del self.models[job_info]

    def on_cur_job_changed(self, job_info):
        self.cur_job = job_info
        self.exportBtn.setEnabled(self.cur_job is not None)
        self.refreshBtn.setEnabled(self.cur_job is not None)
        self.autoRefreshCheckBox.setEnabled(self.cur_job is not None)
        
        if self.cur_job is None: # it means no jobs are left in the local job list
            self.models.clear()
            self.autoRefreshCheckBox.setCheckState(Qt.Unchecked)
            proxy_model = SortFilterProxyModel()
        else:
            proxy_model = self.get_proxy_model(job_info) 
        self.tableView.setModel(proxy_model)
        
    def on_actions_received(self, job_info, actions):
        print "ExecutionLogPage:on_actions_received"
        print len(actions), "are received"
        model = self.get_model(job_info)
        assert isinstance(model, JobLogModel)
        model.add_actions(actions)

    def on_timer_expired(self):
        #print "zero timer expired"
        status = ui.GetMainWindow().synch_data()
        if status: # CANCELED
            self.autoRefreshCheckBox.setCheckState(Qt.Unchecked)

    def on_autoRefreshCheckBox_changed(self, state):
        if state == Qt.Checked:
            self.refreshBtn.setEnabled(False)
            self.zeroTimer.start()
        else:
            self.zeroTimer.stop()
            if self.cur_job:
                self.refreshBtn.setEnabled(True)

    def on_refreshBtn_clicked(self):
        # Call synch_data() in MainWindow class
        ui.GetMainWindow().synch_data()
        
    def on_exportBtn_clicked(self):
        model = self.tableView.model()
        assert isinstance(model, SortFilterProxyModel)
        content = model.get_content()
        dlg = ExportDialog(self)
        ret_val = dlg.exec_()
        if ret_val == QDialog.Accepted:
            file_path = dlg.get_file_path()
            f = open(file_path, "wb")
            f.write(content)
            f.close()
        